package org.openzip.openzip.gui.util.fileTree;

import java.awt.*;
import java.awt.event.*;
import java.io.File;
import java.util.*;
import javax.swing.*;
import javax.swing.tree.*;

import org.openzip.openzip.gui.util.FileUtil;
import org.openzip.openzip.gui.util.checkBox.FileCheckBoxModel;
import org.openzip.openzip.gui.util.checkBox.State;
import org.openzip.openzip.gui.util.fileTree.event.TreeCheckSelectionEvent;
import org.openzip.openzip.gui.util.fileTree.event.TreeCheckSelectionListener;
import org.openzip.openzip.gui.util.selection.SelectedFilesModel;

/**
 * This is an extension of a <code>JTree</code> component that displays the
 * local filesystem in a tree, using <code>FileTreeNode</code> 
 * <code>TreeNode</code> implementation. Each file and folder displayed with a
 * checkbox that indicates whether the selected folder/file is selected. 
 * Beacuse all checkboxes used in the tree are threestate, folder that have
 * only some of their files selected will have their checkbox undecied.<br>
 * This class also provides method for listening to the changes in the 
 * check selection. This is different from normal selection. Check selection
 * gets fired when some of the file or folder in a tree got its checkbox 
 * selected or unselected.
 * 
 * @author Jakub Závěrka
 */
public class CheckboxedTree extends JTree
{
	//list of all checkListeners
	private Collection<TreeCheckSelectionListener> checkListeners;
	//class that holds all selected files
	private SelectedFilesModel globalSelectionModel;
	
	/**
	 * Adds the specified check listener.
	 */
	public void addTreeCheckSelectionListener(
				TreeCheckSelectionListener listener){
		checkListeners.add(listener);
	}
	
	/**
	 * Removes the specified check listener.
	 */
	public void removeTreeCheckSelectionListener(
				TreeCheckSelectionListener listener){
		checkListeners.remove(listener);
	}

	/**
	 * Constructor of the <code>CheckboxedTree</code>.
	 * 
	 * @param rootFile
	 *           The root file of this tree.
	 * @param directoriesOnly
	 *           If the tree displays only directories or both directories and
	 *           files.
	 */
	public CheckboxedTree(File rootFile, boolean directoriesOnly) {
		// creates a new FileTreeNode for the specified root file and pass
		// it to the other constructor.
		this(new FileTreeNode(rootFile, directoriesOnly));
	}
	
	/**
	 * Constructor of the tree with specified node as the root.
	 * 
	 * @param root
	 *           Root node of the tree.
	 */
	private CheckboxedTree(FileTreeNode root){
		super(root);
		//show handles even if the root is hidden
		setShowsRootHandles(true);
		checkListeners = new HashSet<TreeCheckSelectionListener>();
		//renderer for the tree - paints the FileCheckBoxModel 
		setCellRenderer(new FileTreeNodeCellRenderer());
		//listener for the tree - to determine selection and checking
		addMouseListener(new FileTreeNodeMouseListener());
		getSelectionModel().setSelectionMode(
					TreeSelectionModel.SINGLE_TREE_SELECTION);
	}
	
	/**
	 * Will fire the fireTreeCheckSelectionChanged with information provided in
	 * the parameters. Notifies each <code>TreeCheckSelectionListener</code> that
	 * the check selection has changed.
	 * 
	 * @param path
	 *           Path to the modified node.
	 * @param oldState
	 *           Old state on the node's checkbox.
	 * @param state
	 *           New state of the node's checkbox.
	 */
	protected void fireTreeCheckSelectionChanged(TreePath path, State oldState, 
				State state){
		TreeCheckSelectionEvent event = 
			new TreeCheckSelectionEvent(path, this, oldState, state);
		for(TreeCheckSelectionListener listener : checkListeners){
			listener.treeCheckSelectionChanged(event);
		}
	}
	
	/**
	 * Srolls the tree to the provided file and expands all nodes that correspond
	 * to the specified file's parent folders. This method will work only if
	 * directoriesOnly is true (for unpacking, when the directoriesOnly is false,
	 * we do not use this method).
	 * 
	 * @param f File to scroll to.
	 */
	public void scrollToFile(File f){
		//reset the parameter to the parent folder
		if(f.isFile()){
			f = f.getParentFile();
		}
		//split path to folders.
		String[] paths = FileUtil.splitFilePath(f);
		//row in the tree
		int position = 0;
		//for each folder in path, we search for the corresponding node.
		for(int i = 0; i < paths.length; i++){
			String s = "";
			//compare each expanded node to the current folder in search 
			for(;!s.equals(paths[i]);position++){
				s = ((FileTreeNode)getPathForRow(position).
							getLastPathComponent()).getFilePathLast();
			}
			//if we found the node
			if(i != paths.length - 1){
				//we will expand it
				expandRow(position-1);
			}
		}
		//at the end, we will select the node
		setSelectionRow(position-1);
	}

	/**
	 * Self mouse listener for the tree. It's purpose is to allow for node 
	 * checkbox selecting by holding CTRL.
	 */
	public class FileTreeNodeMouseListener extends MouseAdapter {
		
		/**
		 * This method will cause the selected checkbox go the opposite state.
		 * (Selected if unselected, or unselected if selected).
		 */
		@Override public void mouseClicked(MouseEvent e){
			if((e.getModifiers() & MouseEvent.CTRL_MASK) == 2){
				//get the x and y of the click
				int x = e.getX();
				int y = e.getY();

				JTree tree = (JTree)e.getComponent();
				//get path for the click
				TreePath path = tree.getPathForLocation(x, y);
				if(path == null){
					// click outside any node
					return;
				}
				//and last conponent in the path == clicked node
				FileTreeNode node = (FileTreeNode)path.getLastPathComponent();
				//get the checkbox of the node
				FileCheckBoxModel cb = (FileCheckBoxModel)node.getFileCheckBox();
				State oldState = cb.getState();
				//and change its state to next
				cb.nextState();
				//fire the check selection changed event
				fireTreeCheckSelectionChanged(path, oldState, cb.getState());

				// now, we are adding/removing the file represented by clicked
				// checkbox to/from the global selection model

				// Carefully here: checkbox's state has already been changed to
				// reflect user's click on it. Therefore SELECTED means that
				// checkbox has been UNSELECTED and user SELECTED it.
				if(cb.getState() == State.SELECTED){
					if(cb.getFile() != null){
						globalSelectionModel.addFile(cb.getFile());
					}
				}
				// USER cannot set state as UNDECIDED and therefore we assume
				// that if the checkbox wasn't SELECTED, it is UNSELECTED.
				// This means we want to remove the File from our global selection
				// model
				else{
					globalSelectionModel.removeFile(cb.getFile());
				}
				
				tree.repaint();
			}
		}
	}
	
	/**
	 * This class renders nodes in the tree. It uses the
	 * <code>FileCheckBoxModel</code> class.
	 */
	public class FileTreeNodeCellRenderer implements TreeCellRenderer 
	{
		/**
		 * Each node is rendered using the
		 * <code>FileCheckBoxModel.getGraphicalRepresentation()</code>
		 * of its fileCheckBox.
		 */
		@Override
		public Component getTreeCellRendererComponent(JTree tree, Object value,
				boolean sel, boolean expanded, boolean leaf, int row,
				boolean hasFocus) {
			
			//get the current checkbox from the given node
			FileCheckBoxModel currentCheckBox = 
				((FileTreeNode)value).getFileCheckBox();
			//get its graphical representation
			Component val = currentCheckBox.getGraphicalRepresentation();
			
			//selected means blue background, unselected means tree background
			if (sel) {
				val.setBackground(new Color(127, 127, 255));
			}
			else {
				val.setBackground(tree.getBackground());
			}

			return val;
		}
		
	}
	
	/**
	 * Gets the file selection model for this tree.
	 */
	public SelectedFilesModel getGlobalSelectionModel() {
		return globalSelectionModel;
	}
	
	/**
	 * Sets the file selection model for this tree.
	 */
	public void setGlobalSelectionModel(SelectedFilesModel model) {
		globalSelectionModel = model;
	}
}
